﻿using System;
using System.Collections.Generic;
using System.Text;
using AdaptiveAgents.Agents;
using AdaptiveAgents.Games;
using AdaptiveAgents.Distributions;
using AdaptiveAgents.Loggers;
using System.Data;
using AdaptiveAgents.Results;
using System.IO;

namespace AdaptiveAgents.Experiments
{
    /// <summary>
    /// The basic experiment to find out what is the most efficient epsilon to use.
    /// </summary>
    class FromCSVNewAdaptiveExperiment : Experiment
    {
        public FromCSVNewAdaptiveExperiment() : base(50, 3, 1) { }
        private ExperimentResults results;

        private DataTable dtNormal;//A DataTable for a normal run

        /// <summary>
        /// A function to write the XML file as a CSV file ready for opening in excel
        /// </summary>
        /// <param name="fileName">Name of the file</param>
        /// <param name="dt">The data Table to print</param>
        private void WriteToCSV(String fileName, DataTable dt)
        {
            //set an output writer
            System.IO.TextWriter writer = new System.IO.StreamWriter(fileName);

            //print columns name
            for (int i = 0; i < dt.Columns.Count; ++i)
                writer.Write(dt.Columns[i].ToString() + ",");

            writer.WriteLine();

            //for each row...
            for (int i = 0; i < dt.Rows.Count; ++i)
            {
                DataRow row = dt.Rows[i];
                //for each cell in the row...
                for (int j = 0; j < dt.Columns.Count; ++j)
                    writer.Write(row[j].ToString() + ",");
                writer.WriteLine();
            }

            writer.Close();
        }

        /// <summary>
        /// Build the datatable from a CSV file
        /// </summary>
        /// <param name="inFilePath">The path on the hard drive</param>
        /// <param name="outErrors">Exception</param>
        /// <returns>DataTable</returns>
        private DataTable BuildDataTable(string inFilePath, out int outNumOfGames, out string outErrors)
        {
            DataTable myTable = new DataTable("MyTable");
            int i;
            DataRow myRow;
            string[] fieldValues;
            StreamReader myReader = new StreamReader(inFilePath);
            char splitChars = ',';
            outErrors = string.Empty;
            outNumOfGames = 0;

            try
            {
                //Open file and read first line to determine how many fields there are.
                fieldValues = myReader.ReadLine().Split(splitChars);

                //Create data columns accordingly
                for (i = 0; i < fieldValues.Length; i++)
                {
                    myTable.Columns.Add(new DataColumn(fieldValues[i]));
                }

                //Adding the first line of data to data table (which is the column name, can be SKIPPED!!!)
                myRow = myTable.NewRow();
                for (i = 0; i < fieldValues.Length; i++)
                {
                    myRow[i] = fieldValues[i].ToString();
                }
                myTable.Rows.Add(myRow);

                //Now reading the rest of the data to data table
                while (myReader.Peek() != -1)
                {
                    fieldValues = myReader.ReadLine().Split(splitChars);
                    myRow = myTable.NewRow();

                    for (i = 0; i < fieldValues.Length; i++)
                    {
                        myRow[i] = fieldValues[i].ToString();
                    }
                    myTable.Rows.Add(myRow);
                    outNumOfGames++;
                }
            }
            catch (Exception ex)
            {
                #region Test if there is a difference between ex.message to ex.toString()
                //Test if there is a difference between ex.message to ex.toString()... :
                string tempErr = ex.ToString();
                #endregion

                outErrors = ex.Message;
                return new DataTable("Empty");
            }
            finally
            {
                myReader.Close();
            }

            return myTable;
        }

        /// <summary>
        /// Run the main experiment
        /// </summary>       
        public override void runExperiment()
        {
            //Get DataTable for value initialization
            DataTable dtFromCSV = new DataTable();
            string errors = string.Empty;
            int numOfGames;

            //Get generic datatable
            dtFromCSV = BuildDataTable(@"C:\temp1\data.csv", out numOfGames, out errors);

            expRun(numOfGames, dtFromCSV, string.Empty);

            //Get Students DataTable (here students need to put their own CSV (that contains 5 lines except the headers)
            //with the name: firstname_lastName_ID.csv
            string dirPath = @"C:\temp9\";
            errors = string.Empty;
            DataTable dt;
            //List<DataTable> dtArray = new List<DataTable>();
            DirectoryInfo dir = new DirectoryInfo(dirPath);
            FileInfo[] csvFiles = dir.GetFiles("*.csv");

            foreach (FileInfo fi in csvFiles)
            {
                string filePath = string.Concat(dirPath, @"\", fi.Name);//@dirPath + "\" + fi.Name;
                dt = BuildDataTable(filePath, out numOfGames, out errors);
                if (dt != null)
                {
                    expRun(numOfGames, dt, fi.Name.Substring(0, (fi.Name.Length - 4)));
                }
            }
        }

        private void expRun(int inNumOfGames, DataTable dtFromCSV, string inFileName)
        {
            //Create new result file
            results = new ExperimentResults();

            int GAMES = inNumOfGames; //number of games to run
            //int ROUNDS = 500; //number of round each game
            int rounds;
            DataRow row1; //a row for each type of agent

            //Set the competence level, the higher it is - the better the player is.
            double competence0;
            double competence1;
            double competence2;

            //Set the epsilon level
            double eps0;
            double eps1;
            double eps2;

            double utility = 0;

            dtNormal = new DataTable("Normal_Data_Table");
            DataColumn cln = new DataColumn("GameNum", typeof(double));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("Competence0", typeof(double));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("Competence1", typeof(double));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("Competence2", typeof(double));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("eps0", typeof(double));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("eps1", typeof(double));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("eps2", typeof(double));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("avgUtilityOfTheGame", typeof(double));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("Player0NumOfTurnsPlayed", typeof(int));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("Player1NumOfTurnsPlayed", typeof(int));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("Player2NumOfTurnsPlayed", typeof(int));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("Player0AccumUtility", typeof(double));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("Player1AccumUtility", typeof(double));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("Player2AccumUtility", typeof(double));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("Player0AvgUtility", typeof(double));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("Player1AvgUtility", typeof(double));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("Player2AvgUtility", typeof(double));
            dtNormal.Columns.Add(cln);
            cln = new DataColumn("Rounds", typeof(double));
            dtNormal.Columns.Add(cln);

            //If this is the students file, we need to repeat each line 100 times to get some statistics
            int repeat = 1;
            if (!string.IsNullOrEmpty(inFileName))
            {
                repeat = 100;
            }

            //run games
            for (int i = 0; i < GAMES; ++i)
            {
                for (int studentCounter = 0; studentCounter < repeat; studentCounter++)
                {
                    //Get starting values for the epsilon and competence:
                    eps0 = double.Parse(dtFromCSV.Rows[i + 1].ItemArray[7].ToString());
                    eps1 = double.Parse(dtFromCSV.Rows[i + 1].ItemArray[6].ToString());
                    eps2 = double.Parse(dtFromCSV.Rows[i + 1].ItemArray[5].ToString());

                    competence0 = double.Parse(dtFromCSV.Rows[i + 1].ItemArray[4].ToString());
                    competence1 = double.Parse(dtFromCSV.Rows[i + 1].ItemArray[3].ToString());
                    competence2 = double.Parse(dtFromCSV.Rows[i + 1].ItemArray[2].ToString());

                    //Get number of rounds to run the experiment
                    rounds = int.Parse(dtFromCSV.Rows[i + 1].ItemArray[1].ToString());

                    //write to console to see progress
                    if (i % 100 == 0)
                        System.Console.WriteLine(i);

                    //set new rows for the game
                    row1 = dtNormal.NewRow();

                    //enter game number
                    row1["GameNum"] = int.Parse(dtFromCSV.Rows[i + 1].ItemArray[0].ToString()); ;

                    //enter number of rounds
                    row1["Rounds"] = rounds;

                    //save the epsilon value in the DataRows
                    row1["eps0"] = eps0;
                    row1["eps1"] = eps1;
                    row1["eps2"] = eps2;

                    //save the competence value in the DataRows
                    row1["competence0"] = competence0;
                    row1["competence1"] = competence1;
                    row1["competence2"] = competence2;

                    //Create a new environment for the experiment
                    Environment environment = new Environment();

                    //Create a list of 3 agents
                    List<Agent> agents = generatePlayers(3, environment, 1, eps0, eps1, eps2, AdaptiveAgents.logger);

                    //Add agents to the environment
                    for (int j = 0; j < NumAgents; j++)
                    {
                        Agent agent = agents[j];
                        environment.addAgent(agent);
                    }

                    //Create a new normal game
                    //NormalGame game = new NormalGame(environment, 10);
                    NormalGameToCSV game = (NormalGameToCSV)GameFactory.create(Games.GameType.NormalToCsv, environment, rounds);

                    //Set the competence level for each agent
                    environment.agents[0].Competence = new Blocks(competence0);
                    environment.agents[1].Competence = new Blocks(competence1);
                    environment.agents[2].Competence = new Blocks(competence2);

                    //enter to the game the row it needs to write the game data to
                    game.Dr1 = row1;

                    //Start the game
                    utility = game.start(AdaptiveAgents.logger, out results);

                    row1["avgUtilityOfTheGame"] = utility;
                    row1["Player0NumOfTurnsPlayed"] = results.numberOfTurnsPlayer0Played;
                    row1["Player1NumOfTurnsPlayed"] = results.numberOfTurnsPlayer1Played;
                    row1["Player2NumOfTurnsPlayed"] = results.numberOfTurnsPlayer2Played;

                    row1["Player0AccumUtility"] = results.player0AccumilatingProfit;
                    row1["Player1AccumUtility"] = results.player1AccumilatingProfit;
                    row1["Player2AccumUtility"] = results.player2AccumilatingProfit;

                    row1["Player0AvgUtility"] = results.player0AccumilatingProfit / results.numberOfTurnsPlayer0Played;
                    row1["Player1AvgUtility"] = results.player1AccumilatingProfit / results.numberOfTurnsPlayer1Played;
                    row1["Player2AvgUtility"] = results.player2AccumilatingProfit / results.numberOfTurnsPlayer2Played;

                    dtNormal.Rows.Add(row1);

                    //Print to screen current utility if needed (cancel to increase speed)
                    //System.Console.WriteLine(utility);
                }
            }
            //create file names and write to the files
            String stamp = DateTime.Now.ToString("yyyy.MM.dd-HH.mm.ss");
            //dtNormal.WriteXml("FindBestEpsilonExperiment_" + stamp + ".csv");

            string studentName = "Tal_Doron_036948859";
            if (string.IsNullOrEmpty(inFileName))
            {
                WriteToCSV(studentName + "_" + stamp + ".csv", dtNormal);
            }
            else
            {
                WriteToCSV("exec_" + studentName + "_" + inFileName + "_" + stamp + ".csv", dtNormal);
            }

        }

        protected List<Agent> generatePlayers(int numOfAgents, Environment env, int numAdaptive, double adaptiveEpsilon, double normalEpsilon1, double normalEpsilon2, Logger logger)
        {
            List<Agent> retList = new List<Agent>(numOfAgents);

            AgentDirector director = new AgentDirector();

            //create adaptive agent
            director.Builder = new Agents.NewAdaptiveBuilder();
            director.construct(0, env, adaptiveEpsilon, logger);
            retList.Add(director.getAgent());


            //create normal (epsilon greedy) agent1
            director.Builder = new Agents.NormalBuilder();
            director.construct(1, env, normalEpsilon1, logger);
            retList.Add(director.getAgent());

            //create normal (epsilon greedy) agent2
            director.Builder = new Agents.NormalBuilder();
            director.construct(2, env, normalEpsilon2, logger);
            retList.Add(director.getAgent());

            return retList;
        }
    }
}
